package main

import (
	"fmt"
	"github.com/Unknwon/goconfig"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	// "net/url"
	_ "net/http/pprof"
	"os"
	"runtime"
	"sort"
	"strings"
	"time"
)

var p = fmt.Println
var ini iniType

func init() {
	ini = intInit()
	// go func() {
	// 	fmt.Println(http.ListenAndServe("localhost:8454", nil))
	// }()
}

func main() {
	http.HandleFunc("/", handerAll)
	binding := ":" + ini.port
	p("goproxy started on port :", binding)
	err := http.ListenAndServe(binding, nil)
	if err != nil {
		p("[ERROR] ERROR: ", err)
		return
	}
}

func handerAll(respWriter http.ResponseWriter, req *http.Request) {
	defer func() { // 必须要先声明defer，否则不能捕获到panic异常
		if err := recover(); err != nil {
			fmt.Println(err) // 这里的err其实就是panic传入的内容，55
		}
	}()

	fmt.Print("*")
	defer req.Body.Close()
	// p("********from ", req.RemoteAddr)
	// p(req.RequestURI)
	if req.Host == "test.com" {
		runtime.GC()
		respWriter.Write([]byte("go proxy running."))
		ini = intInit()
		return
	}
	if !isAllowedIp(req.RemoteAddr) {
		p("Forbidden:req.RemoteAddr", req.RemoteAddr)
		respWriter.Write([]byte("403 Forbidden"))
		return
	}
	respWriter.Header().Set("goproxy", "wkc_go_proxy")
	director := func(req *http.Request) {}
	proxy := getTransportFieldURL()
	// refresh time :ns
	reverseProxy := httputil.ReverseProxy{Director: director,
		Transport: proxy,
		// FlushInterval: 100000000,
	}
	reverseProxy.ServeHTTP(respWriter, req)

}

func getTransportFieldURL() (transport *http.Transport) {
	transport = &http.Transport{Dial: myDial}
	return
}

func myDial(network, addr string) (net.Conn, error) {
	// p("gorouting:", runtime.NumGoroutine())
	var err error
	var conn net.Conn
	var d net.Dialer
	// set Timeout 30s
	d.Timeout = time.Second * 5
	for i := 0; i < 1; i++ {
		d.LocalAddr, err = net.ResolveTCPAddr("tcp4", randLocalAddr())
		conn, err = d.Dial(network, addr)
		if err == nil {
			return conn, err
		}
		fmt.Print("#")
	}
	p("\n#Dial ", network, addr, d.LocalAddr)
	return conn, err
}

func randLocalAddr() string {
	r := ini.localIp[rand.Int()%len(ini.localIp)]
	//p("********local ip", r)
	return r
}

type iniType struct {
	port      string
	localIp   []string
	proxy     []string
	allowedIp map[string]bool
}

func intInit() iniType {
	var t iniType
	ini, err := goconfig.LoadConfigFile("goproxy.ini")
	if err != nil {
		fmt.Println(err)
		os.Exit(0)
	}
	t.port, _ = ini.GetValue("port", "port")
	localIp, _ := ini.GetSection("local ip")
	allowedIp, _ := ini.GetSection("allowed ip")

	for _, value := range localIp {
		t.localIp = append(t.localIp, value)
	}

	t.allowedIp = make(map[string]bool)
	for _, value := range allowedIp {
		t.allowedIp[value] = true
	}
	sort.Strings(t.localIp)
	sort.Strings(t.proxy)
	p("Local IP :")
	for _, ip := range t.localIp {
		p("\t" + ip)
	}
	p("Allowed IP :")
	for ip, _ := range t.allowedIp {
		p("\t" + ip)
	}
	return t
}

func isAllowedIp(ip string) bool {
	ip = strings.Split(ip, ":")[0]
	_, ok := ini.allowedIp[ip]
	return ok
}
